Redis API的使用和理解
- 通用命令
- 字符串类型
- 哈希类型
- 列表类型
- 集合类型
- 有序集合类型
通用命令和数据结构
通用命令
keys
遍历所有的key
API:
keys [pattern]
keys命令一般不在生产环境使用
- keys是一个比较重的命令, 复杂度为O(n), Redis是一个单线程的, 所以此命令会阻塞其他命令
- 本身在生产环境中使用此命令没有太大意义, 如若确有此需求 可以使用scan命令
keys * 怎么用
- 热备从节点 (此节点一般不会部署到生产环境中)
- scan
演示1:
[root@localhost soft]# redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> set name Jiavg OK 127.0.0.1:6379> set java best OK 127.0.0.1:6379> keys * 1) "name" 2) "hello" 3) "java" 127.0.0.1:6379> dbsize (integer) 3
演示2:
127.0.0.1:6379> mset hello world hehe haha php good phe his OK 127.0.0.1:6379> keys he* 1) "hehe" 2) "hello" 127.0.0.1:6379> keys he[a-h]* 1) "hehe" 127.0.0.1:6379> keys ph? 1) "phe" 2) "php"
dbsize
计算key的总数
API:
dbsize
- dbsize可以在线上使用, Redis内置一个计数器, 会实时更新, 故其时间复杂度为O(1)
演示
127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 k4 v4 OK 127.0.0.1:6379> dbsize (integer) 4 127.0.0.1:6379> sadd a b c d (integer) 3 127.0.0.1:6379> dbsize (integer) 5
exists
检查key是否存在
API:
exists key1 [key2 ...]
存在: 返回1, 不存在: 返回0
一般情况下可以随意使用, 时间复杂度为O(1), 但是存在特殊情况, 后面将会介绍
演示:
127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> exists k1 (integer) 1 127.0.0.1:6379> del k1 (integer) 1 127.0.0.1:6379> exists k1 (integer) 0
del
删除指定的key-value
API:
del key1 [key2 ...]
- 删除成功: 返回1, 删除失败: 返回0
演示:
127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> mset k1 v1 k2 v2 OK 127.0.0.1:6379> get k1 "v1" 127.0.0.1:6379> del k1 (integer) 1 127.0.0.1:6379> get k1 (nil) 127.0.0.1:6379> del k3 (integer) 0
expire, ttl, persist
expire
API:
# 除了指定时间间隔, 也可以指定时间戳 expire key seconds
- 指定key过期时间
ttl
API:
ttl key
- 查看key剩余过期时间
- 返回-2: 代表key已经不存在
- 返回-1: 代表key存在, 并且没有过期时间
- 返回正整数: 代表key距离过期时间的秒数
- 查看key剩余过期时间
persist
API:
persist key
- 去掉key的过期时间
示例:
127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> expire k1 10 (integer) 1 127.0.0.1:6379> ttl k1 (integer) 6 127.0.0.1:6379> ttl k1 (integer) 2 127.0.0.1:6379> ttl k1 (integer) -2 127.0.0.1:6379> ttl k1 (integer) -2 127.0.0.1:6379> set k2 v2 OK 127.0.0.1:6379> expire k2 10 (integer) 1 127.0.0.1:6379> persist k2 (integer) 1 127.0.0.1:6379> ttl k2 (integer) -1
type
返回key的类型
API:
type key
- 返回值有: string, hash, list, set, zset, none(代表key不存在)
演示:
127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> type k1 string 127.0.0.1:6379> sadd myset a b c (integer) 3 127.0.0.1:6379> type myset set
时间复杂度
数据结构和内部编码
Redis Object
线程模型
单线程
单线程为什么这么快?
- 纯内存
- 非阻塞IO
- 避免线程切换和竞态消耗
注意事项
一次只运行一条命令
拒绝长慢命令
(keys, flushall, flushdb, slow lua script, mutil/exec, operate big value (collection)
其实不是单线程
redis也是多线程的, 但是redis的主线程处理业务, 而其他三个线程跟主要功能是关系不到的.
redis为了保证其高效, 一些比较耗时的动作会起线程或者进程来完成, 不会阻塞在业务主线程上.
(例如: fysnc file descriptor 和 close file descriptor会开起新线程)
字符串
字符串键值结构
字符串使用场景
- 缓存
- 计数器
- 分布式锁
- ……
get, set, del
# 获取key对应的value O(1)时间复杂度
get key
# 设置key-value O(1)时间复杂度
set key value
# 删除key-value O(1)时间复杂度
del key
- 演示:
127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379> set hello "world"
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
incr, decr, incrby, decrby
# key自增1, 如果key不存在, 自增后get(key)=1 O(1)时间复杂度
incr key
# key自减1, 如果key不存在, 自增后get(key)=-1 O(1)时间复杂度
decr key
# key自增k, 如果key不存在, 自增后get(key)=k O(1)时间复杂度
incrby key k
# key自减k, 如果key不存在, 自增后get(key)=-k O(1)时间复杂度
decrby key k
- 演示:
127.0.0.1:6379> get counter
(nil)
127.0.0.1:6379> incr counter
(integer) 1
127.0.0.1:6379> get counter
"1"
127.0.0.1:6379> incrby counter 99
(integer) 100
127.0.0.1:6379> decrby counter 50
(integer) 50
127.0.0.1:6379> decr counter
(integer) 49
实战
实现如下功能:
记录网站每个用户个人主页的访问量?
incr userid:pageview
(单线程, 无竞争)实现如下功能:
缓存视频的基本信息 (数据源在MySQL中)伪代码?
实现如下功能:
分布式id生成器?
incr id
(原子操作 [基础思路, 实际操作较为复杂])
set, setnx, set xx
# 不管key是否存在, 都设置 O(1)时间复杂度
set key value
# key不存在, 才设置 O(1)时间复杂度
setnx key value
# key存在, 才设置 O(1)时间复杂度
set key value xx
# 将value关联到key, 并将key的生存时间设为seconds(以秒为单位)。
# 将set命令和expire命令结合为原子操作, 在做一些分布式锁方面很有用
# O(1)时间复杂度
setex key seconds value
- 演示:
127.0.0.1:6379> exists name
(integer) 0
127.0.0.1:6379> set name Jiavg
OK
127.0.0.1:6379> setnx name lylgjiavg
(integer) 0
127.0.0.1:6379> setnx username jiavag
(integer) 1
127.0.0.1:6379> set name root xx
OK
127.0.0.1:6379> set name1 root xx
(nil)
127.0.0.1:6379> setex school 10 lylg
OK
127.0.0.1:6379> ttl school
(integer) 4
mget, mset
# 批量获取key, 原子操作 时间复杂度O(n): n为获得key的数量
mget key1 key2 key3 [key4...]
# 批量设置key-value 时间复杂度O(n): n为获得key的数量
mset key1 value1 key2 value2 key3 value3
- 示例:
127.0.0.1:6379> mset name jiavg school lylg addr henan
OK
127.0.0.1:6379> mget name school addr
1) "jiavg"
2) "lylg"
3) "henan"
- n次get
- 1次mget
getset, append, strlen
# set key newvalue 并返回旧的value O(1)时间复杂度
getset key newvalue
# 将value追加到旧的value O(1)时间复杂度
append key value
# 返回字符串的长度 (注意中文) O(1)时间复杂度
strlen key
- 示例
127.0.0.1:6379> set name jiavg
OK
127.0.0.1:6379> getset name lylgjiavg
"jiavg"
127.0.0.1:6379> append name ", hi!"
(integer) 14
127.0.0.1:6379> get name
"lylgjiavg, hi!"
127.0.0.1:6379> strlen name
(integer) 14
127.0.0.1:6379> set hello "你好"
OK
127.0.0.1:6379> strlen hello
(integer) 6
incrbyfloat, getrange, setrange
# 增加key对应的浮点值 O(1)时间复杂度
incrbyfloat key floatvalue
# 获取字符串指定下标所有的值 O(1)时间复杂度
getrange key start end
# 设置指定下标所有对应的值 O(1)时间复杂度
setrange key index value
- 示例:
127.0.0.1:6379> set counter 1
OK
127.0.0.1:6379> incrbyfloat counter 1.5
"2.5"
127.0.0.1:6379> get counter
"2.5"
127.0.0.1:6379> set username lylgjiavg
OK
127.0.0.1:6379> getrange username 0 3
"lylg"
127.0.0.1:6379> setrange username 7 n
(integer) 9
127.0.0.1:6379> get username
"lylgjiang"
字符串总结
哈希
哈希键值结构
特点
- Mapmap (即map中含有map的结构)
- field不能相同, value可以相同
hget, hset, hdel
# 获取hash key 对应的field的value O(1)时间复杂度
hget key field
# 设置hash key对应的field的value O(1)时间复杂度
hset key field value
# 删除hash key对应field的value O(1)时间复杂度
hdel key field
- 示例:
127.0.0.1:6379> hset user:1:info name jiavg
(integer) 1
127.0.0.1:6379> hset user:1:info age 22
(integer) 1
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "jiavg"
3) "age"
4) "22"
127.0.0.1:6379> hdel user:1:info age
(integer) 1
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "jiavg"
hexists, hlen
# 判断hash key是否有field O(1)时间复杂度
hexist key field
# 获取hash key field的数量 O(1)时间复杂度
hlen key
- 示例:
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "jiavg"
3) "age"
4) "22"
127.0.0.1:6379> hexists user:1:info age
(integer) 1
127.0.0.1:6379> hlen user:1:info
(integer) 2
hmget, hmset
# 批量获取hash key的一批field对应的值 时间复杂度O(n): n为获得key的数量
hmget key field1 field2 [field3...]
# 批量设置hash key的一批field value 时间复杂度O(n): n为获得key的数量
hmset key field1 value1 field2 value2 [field3 value3...]
- 示例
127.0.0.1:6379> hmset user:1:addr addr1 china addr2 henan addr3 zhoukou
OK
127.0.0.1:6379> hmget user:1:addr addr1 addr3
1) "china"
2) "zhoukou"
实战
实现如下功能:
记录网站每个用户个人主页的访问量?
hincrby user:1:info pageview count
实现如下功能:
缓存视频的基本信息(数据源在MySQL中)伪代码?
hgetall, hvals, hkeys
# 返回hash key对应的所有field和value
# 时间复杂度O(n): n为获得key的数量
hgetall key
# 返回hash key对应的所有field的value
# 时间复杂度O(n): n为获得key的数量
hvals key
# 返回hash key对应所有的field
# 时间复杂度O(n): n为获得key的数量
hkeys key
- 示例:
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "jiavg"
3) "age"
4) "22"
127.0.0.1:6379> hvals user:1:info
1) "jiavg"
2) "22"
127.0.0.1:6379> hkeys user:1:info
1) "name"
2) "age"
小心使用hgetall
- 小心使用, 牢记单线程
- 如果hgetall的key的field很多将会造成其他命令阻塞
字符串 vs 哈希
- 相似的API
如何存储用户属性?
用户信息 (string v1 实现)
用户信息 (string v2 实现)
用户信息 (hash 实现)
三种方案比较
列表
列表结构
特点
- 有序
- 可以重复
- 左右两边插入弹出
rpush, lpush
# 从列表右端插入值 (1~n个) 时间复杂度O(1~n)
rpush key value1 value2 [value3...]
# 从列表左端插入值 (1~n个) 时间复杂度O(1~n)
lpush key value1 value2 [value3...]
linsert
# 在list指定的值前|后插入newvalue
# 时间复杂度O(n)
linsert key before|after value newvalue
lpop, rpop
# 从列表左侧弹出一个item
# 时间复杂度O(1)
lpop key
# 从列表右侧弹出一个item
# 时间复杂度O(1)
rpop key
lrem
ltrim
# 按照索引范围修剪列表
# 时间复杂度O(n)
ltrim key start end
lrange
lindex
# 获取列表指定索引的item
# 时间复杂度O(n)
lindex key index
llen
# 获取列表长度
# 时间复杂度O(1)
llen key
lset
# 设置列表指定索引值为newvalue
# 时间复杂度O(n)
lset key index newvalue
演示
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> rpush mylist d e f
(integer) 6
127.0.0.1:6379> lrange mylist 1 -1
1) "b"
2) "a"
3) "d"
4) "e"
5) "f"
127.0.0.1:6379> lrange mylist 0 -1
1) "c"
2) "b"
3) "a"
4) "d"
5) "e"
6) "f"
127.0.0.1:6379> lpop mylist
"c"
127.0.0.1:6379> rpop mylist
"f"
127.0.0.1:6379> lrange mylist 0 -1
1) "b"
2) "a"
3) "d"
4) "e"
127.0.0.1:6379> linsert mylist before a a1
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "b"
2) "a1"
3) "a"
4) "d"
5) "e"
127.0.0.1:6379> lrem mylist 0 a
(integer) 1
127.0.0.1:6379> lrange mylist 0 -1
1) "b"
2) "a1"
3) "d"
4) "e"
127.0.0.1:6379> ltrim mylist 1 3
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "a1"
2) "d"
3) "e"
127.0.0.1:6379> lindex mylist 0
"a1"
127.0.0.1:6379> llen mylist
(integer) 3
127.0.0.1:6379> lset mylist 0 a
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "a"
2) "d"
3) "e"
blpop, brpop
# lpop阻塞版本, timeout是阻塞超时时间, timeout=0为永远不阻塞
# 时间复杂度O(1)
blpop key timeout
# rpop阻塞版本, timeout是阻塞超时时间, timeout=0为永远不阻塞
# 时间复杂度O(1)
brpop key timeout
TIPS
- lpush + lpop = Stack
- lpush + rpop = Queue
- lpush + ltrim = Capped Collection
- lpush + brpop = Message Queue
集合
集合结构
特点
- 无序
- 无重复
- 支持集合间操作
sadd, srem
# 向集合key添加1element(如果element已经存在, 添加失败)
# 时间复杂度O(1)
sadd key element
# 将集合key中的element移除掉
# 时间复杂度O(1)
srem key element
scard, sismember, srandmember, spop, smembers
- 小心使用smembers
演示
127.0.0.1:6379> sadd names jiavg znc lx ycg
(integer) 4
127.0.0.1:6379> smembers names
1) "lx"
2) "ycg"
3) "jiavg"
4) "znc"
127.0.0.1:6379> spop names
"lx"
127.0.0.1:6379> smembers names
1) "ycg"
2) "jiavg"
3) "znc"
127.0.0.1:6379> scard names
(integer) 3
127.0.0.1:6379> sismember names lx
(integer) 0
实战
- 抽奖系统
- Like, 赞, 踩
- 标签
集合间API (sdiff, sinter, sunion)
集合间实战
- 共同关注
TIPS
- sadd = tagging
- spop/srandmember = random item
- sadd + sinter = social graph
有序集合
有序集合结构
集合 vs 有序集合
列表 vs 有序集合
zadd
# 添加score和element, element不可重复, score可以重复(可以多对)
# 时间复杂度O(logN)
zadd key score element
zrem
# 删除元素 (可以是多个)
# 时间复杂度O(1)
zrem key element
zscore
# 返回元素的分数
# 时间复杂度O(1)
zscore key element
zincrby
# 增加或减少元素的分数
# 时间复杂度O(1)
zincrby key increScore element
zcard
# 返回元素的总个数
# 时间复杂度O(1)
zcard key
zrange
# 返回指定索引范围内的升序元素[是否显示分值]
# 时间复杂度O(logn + m): n指有序集合元素个数, m指获取范围内元素个数
zrange key start end [withscores]
zrangebyscore
# 返回指定分数范围内的升序元素[是否显示分值]
# 时间复杂度O(logn + m): n指有序集合元素个数, m指获取范围内元素个数
zrange key minscore maxscore [withscores]
zcount
# 返回有序集合指定分数范围内的元素个数
# 时间复杂度O(logn + m): n指有序集合元素个数, m指获取范围内元素个数
zcount key minscore maxscore
zremrangebyrank
# 删除指定排名内的升序元素
# 时间复杂度O(logn + m): n指有序集合元素个数, m指删除范围内元素个数
zremrangebyrank key start end
zremrangebyscore
# 删除指定分数内的升序元素
# 时间复杂度O(logn + m): n指有序集合元素个数, m指删除范围内元素个数
zremrangebyscore key minscore maxscore
演示
实战
- 排行榜
score: timestamp, salecount, followcount
查漏补缺
- zrevrank
- zrevrange
- zrevrangebyscore
- zinterstore
- zunionstore